package quarto.view.client;

import quarto.model.Piece;
import quarto.model.InvalidPieceValueException;
import quarto.model.Board;
import quarto.controller.client.Client;
import quarto.controller.client.Player;
import quarto.controller.client.HumanPlayer;
import quarto.controller.client.ComputerPlayer;
import quarto.controller.client.strategy.*;
import quarto.controller.Protocol;
import quarto.Message;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.TreeMap;
import javax.swing.border.*;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;


/**
 * Grafische representatie van de client user interface
 * @knows
 * - De Client
 * @does
 * - Grafische representatie van het spel weergeven
 * - Geeft aan Client door welke handelingen de Player verricht
 * @author Frank Borgonjen & Tim Smit
 */
public class ClientGUI extends JFrame implements ClientUI, ActionListener, ChangeListener
{
    /** locatie van de afbeeldingen */
    public static final String  IMAGES_DIR   = "images/";
    
    /** aantal plekken op het bord */
    public static final int     NRPIECES     = Board.DIM * Board.DIM;
    
    /** de client */
    private Client              client;
    
    /** de player */
    private Player              player;
    
    /** de naam van de speler */
    private String              playerName;
    
    /** soort van speler, een menselijke of een computer */
    private String              kindOfPlayer;
    
    /** het poort nummer van de server */
    private int                 portNumber;
    
    /** het serveradres */
    private InetAddress         hostAddress;
    
    /** is er al een geconnect */
    private boolean             connected;
    
    /** moet er afgesloten worden */
    private boolean             shutdown;

    /**
     * Variabelen voor opbouw van de GUI
     */
    private TreeMap<String,ImageIcon> images;   // treemap met plaatjes van de stukken
    private ImageIcon           emptyImg = new ImageIcon(IMAGES_DIR + "empty.png");
    private ArrayList<JButton>  boardButtons;    // arraylist met buttons van het bord
    private ArrayList<JButton>  pieceButtons;    // arraylist met buttons van beschikbare stukken
    private JPanel              boardPanel;      // panel voor het bord
    private JPanel              piecePanel;      // panel voor de beschikbare stukken
    private JTextArea           TAMessages;      // Textarea voor de berichten
    private JTextField          TFMessage;       // Textfield om te chatten
    private JPanel              strategyStrength;// Panel om sterkte van computer aan te geven
    private JLabel              LBCurrentPiece;  // label waar het huidige stuk in staat
    private JLabel              LBOpponentPiece; // label waar het stuk voor de tegenstander in staat
    private JLabel              LBYourName;      // label met de spelersnaam
    private JLabel              LBOpponentName;  // label met de spelersnaam van de tegenstander
    private Color               boardPieceColor = new Color(250, 250, 250);  // kleur voor de knoppen
    private Color               highlightColor = new Color(200, 200, 255);   // kleur voor de hint
    private ArrayList<Strategy> strategies;      // arraylist met strategieen
    private Strategy            currentStrategy; // houdt de huidige strategie bij
    private JSlider             JSStrategies;    // slider voor de strategieen
    private JMenuItem           JMIReconnet;     // menu item voor het reconnecten
    private JMenu               JMHint;          // menu voor de hints
    
    /** ben je aan de beurt, om het stuk te plaatsen */
    private Piece               currentPiece;
    
    /** gekozen locatie voor het stuk */ 
    private int                 locationForPiece;
    
    /** mag je een stuk selecteren */
    private boolean             selectPiece;
    
    /** het nieuwe stuk */
    private Piece               newPiece;
    
    
    /**
     * construeert een nieuw ClientGUI object.
     */
    public ClientGUI()
    {
        super(Message.CLIENTGUI_TITLE);
        
        // de mogelijke strategieen
        this.strategies = new ArrayList<Strategy>();
        this.strategies.add(new DumbStrategy());
        this.strategies.add(new SmartStrategy());
        this.strategies.add(new PerfectStrategy());
        
        // bouw de GUI
        this.buildGUI();
        
        // we zijn nog niet geconnect
        this.connected    = false;
        
        // initialisatie van gebruikersgegevens
        this.playerName   = null;
        this.kindOfPlayer = null;
        this.hostAddress  = null;
        this.portNumber   = -1;
        this.shutdown     = false;
        
        // laat het connectie dialoog zien
        this.showConnectDialog();
        
        this.setVisible(true);
        
        // standaard niks doen bij het sluiten
        this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        
        // 'luisteren' of het scherm wordt afgesloten
        this.addWindowListener(new WindowAdapter()
        {
            @Override
            public void windowClosing(WindowEvent e)
            {
                askForClose();
            }
        });
    }
    
    /**
     * Vraagt de gebruiker of er afgesloten moet worden
     */
    public void askForClose()
    {                
        // vragen op er gestopt moet worden
        int response = JOptionPane.showConfirmDialog(
            null,
            Message.CLIENTGUI_CONFIRM_CLOSE_MESSAGE,
            Message.CLIENTGUI_CONFIRM_CLOSE_TITLE,
            JOptionPane.YES_NO_OPTION,
            JOptionPane.QUESTION_MESSAGE);

        // moet er gestopt worden
        if (response == JOptionPane.YES_OPTION)
        {
            // stoppen
            if (this.connected)
            {
                this.shutdown = true;
                this.client.shutdown();
            }

            System.exit(0);
        }
    }
    
    /**
     * Bouw de daadwerkelijke GUI
     */
    private void buildGUI()
    {
        // formaat en positie op het scherm
        // moet naar 940 om het op een windows bak enigszins tof te krijgen
        this.setSize(940, 650);
        this.setLocationRelativeTo(null);
        
        // bovenste panel waar het bord en de stukken in komen
        JPanel topPanel = new JPanel();
        topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.X_AXIS));
        
        // panels die het bord en de beschikbare stukken representeren.
        // vullen met buttons en icons en daarna toevoegen aan topPanel
        boardPanel = new JPanel(new GridLayout(Board.DIM, Board.DIM, 5, 5));
        piecePanel = new JPanel(new GridLayout(Board.DIM, Board.DIM, 5, 5));
        fillBoardAndPiecePanel();
        topPanel.add(boardPanel);
        topPanel.add(Box.createHorizontalStrut(10));
        topPanel.add(piecePanel);
        
        
        // panel voor status berichten en panels met huidige stukken
        JPanel middlePanel = new JPanel();
        middlePanel.setLayout(new BoxLayout(middlePanel, BoxLayout.X_AXIS));
        
        // Scrollpane met daarin een Textarea voor de statusberichten
        TAMessages = new JTextArea();
        TAMessages.setEditable(false);
		TAMessages.setLineWrap(true);
		TAMessages.setWrapStyleWord(true);
		JScrollPane SPMessages = new JScrollPane(TAMessages);
		SPMessages.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        TAMessages.setMinimumSize(new Dimension(300, 100));
        TAMessages.setMinimumSize(new Dimension(1000, 300));

        // chat textfield
        TFMessage = new JTextField();
        TFMessage.addActionListener(this);
        TFMessage.setMinimumSize(new Dimension(300, 30));
        TFMessage.setMaximumSize(new Dimension(1600, 30));
        TFMessage.setPreferredSize(new Dimension(500, 30));
        
        JPanel middleLeft = new JPanel();
        middleLeft.setLayout(new BoxLayout(middleLeft, BoxLayout.Y_AXIS));
        middleLeft.setBorder(BorderFactory.createLineBorder(Color.GRAY));
        middleLeft.add(SPMessages);
        middleLeft.add(TFMessage);
        
        // panel met de namen van de spelers
        JPanel namePanel = new JPanel(new GridLayout(2, 2, 5, 5));
        namePanel.setBackground(Color.WHITE);
        
        // naam van deze speler
        JLabel LBJouwnaamDesc = new JLabel(Message.CLIENTGUI_OWN_NAME + ":");
        LBYourName = new JLabel();
        LBJouwnaamDesc.setLabelFor(LBYourName);
        namePanel.add(LBJouwnaamDesc);
        namePanel.add(LBYourName);
        
        // naam van de tegenstander
        JLabel LBOpponentDesc = new JLabel(Message.CLIENTGUI_OPPONENT_NAME + ":");
        LBOpponentName = new JLabel();
        LBOpponentDesc.setLabelFor(LBOpponentName);
        namePanel.add(LBOpponentDesc);
        namePanel.add(LBOpponentName);
        
        // panel voor de huidige stukken
        JPanel currPiecesPanel = new JPanel(new GridLayout(2,2));
        currPiecesPanel.setBackground(Color.WHITE);
        
        JLabel LBCurrentPieceDesc = new JLabel(Message.CLIENTGUI_OWN_PIECE + ":");
        LBCurrentPieceDesc.setVerticalAlignment(JLabel.TOP);
        LBCurrentPiece = new JLabel(this.emptyImg);
        LBCurrentPiece.setPreferredSize(new Dimension(75,75));
        currPiecesPanel.add(LBCurrentPieceDesc);
        currPiecesPanel.add(LBCurrentPiece);
        
        JLabel LBOpponentPieceDesc = new JLabel(Message.CLIENTGUI_OPPONENT_PIECE + ":");
        LBOpponentPieceDesc.setVerticalAlignment(JLabel.TOP);
        LBOpponentPiece = new JLabel(this.emptyImg);
        LBOpponentPiece.setPreferredSize(new Dimension(75,75));
        LBOpponentPiece.setAlignmentX(Component.CENTER_ALIGNMENT);
        currPiecesPanel.add(LBOpponentPieceDesc);
        currPiecesPanel.add(LBOpponentPiece);
        
        
        JPanel middleRight = new JPanel();
        middleRight.setLayout(new BoxLayout(middleRight, BoxLayout.Y_AXIS));
        EmptyBorder e = new EmptyBorder(5, 5, 5, 5);            // de margin
        Border b = BorderFactory.createLineBorder(Color.GRAY);  // het lijntje
        middleRight.setBorder(new CompoundBorder(b, e));        // borders samenvoegen
        middleRight.setBackground(Color.WHITE);                 // witte achtergrond
        
        // panel voor de strategie
        strategyStrength = new JPanel(new GridLayout(1, 2, 5, 5));
        strategyStrength.setBackground(Color.WHITE);
        
        // slider voor de strategie
        int nrstrats = this.strategies.size();
        this.JSStrategies = new JSlider(1, nrstrats, 1);
        this.JSStrategies.setMajorTickSpacing(1);
        this.JSStrategies.setValue(1);
        this.JSStrategies.setSnapToTicks(true);
        this.JSStrategies.setPaintLabels(true);
        this.JSStrategies.addChangeListener(this);

        // toevoegen aan panel
        strategyStrength.add(new JLabel(Message.CLIENTGUI_AI_STRENGTH + ":"));
        strategyStrength.add(this.JSStrategies);

        middleRight.add(namePanel);
        middleRight.add(strategyStrength);
        middleRight.add(Box.createVerticalStrut(10));
        middleRight.add(currPiecesPanel);
        
        // textarea en rechter panel toevoegen
        middlePanel.add(middleLeft);
        middlePanel.add(Box.createHorizontalStrut(10));
        middlePanel.add(middleRight);

        
        // maak de menu bar aan
        JMenuBar menuBar = new JMenuBar();

        // het eerste menu
        JMenu bestand = new JMenu(Message.CLIENTGUI_MENU_FILE);
        menuBar.add(bestand);

        // opnieuw verbinden
        this.JMIReconnet = new JMenuItem(Message.CLIENTGUI_MENU_FILE_RECONNECT);
        this.JMIReconnet.addActionListener(new ActionAdapter()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                // er kan alleen opnieuw verbonden worden als er geen
                // client actief is
                if (!client.isAlive())
                {
                    if (connected)
                    {
                        resetBoardAndPieces();
                        connect();
                    }
                    else
                    {
                        showConnectDialog();
                    }
                    
                    JMIReconnet.setEnabled(false);
                }
                else
                {
                    errorMessage(Message.CLIENTGUI_ERROR_ACTIVE_GAME);
                }
            }
        });
        this.JMIReconnet.setEnabled(false);
        bestand.add(this.JMIReconnet);
        
        // instellingen wijzigen alvorens opnieuw te verbinden
        JMenuItem item = new JMenuItem(Message.CLIENTGUI_MENU_FILE_ASK_CONNECT);
        item.addActionListener(new ActionAdapter()
        {
            public void actionPerformed(ActionEvent e)
            {
                showConnectDialog();
            }
        });
        bestand.add(item);
        
        bestand.add(new JSeparator());
        
        // aflsuiten knopje
        item = new JMenuItem(Message.CLIENTGUI_MENU_FILE_CLOSE);
        item.addActionListener(new ActionAdapter()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                askForClose();
            }
        });
        bestand.add(item);
        
        // het hint menu
        this.JMHint = new JMenu(Message.CLIENTGUI_MENU_HINTS);
        menuBar.add(this.JMHint);
        
        JMenuItem stratItem;
        for (Strategy strat : this.strategies)
        {
            stratItem = new JMenuItem(strat.getName());
            stratItem.addActionListener(new ActionAdapter()
            {
                @Override
                public void actionPerformed(ActionEvent e)
                {
                    int found = -1;
                    int i = 0;
                    while (i < strategies.size() && found == -1)
                    {
                        if (((JMenuItem)e.getSource()).getText().equals(strategies.get(i).getName()))
                        {
                            found = i;
                            strategies.get(found).setClient(client);
                        }
                        
                        i++;
                    }
                    
                    if (found != -1)
                    {
                        if (selectPiece)
                        {
                            Piece piece = strategies.get(found).getNewPiece();
                            setHighlight(piece);
                        }
                        else if (currentPiece != null)
                        {
                            int location = strategies.get(found).getLocationForPiece(currentPiece);
                            setHighlight(location);
                        }
                        else
                        {
                            errorMessage(Message.CLIENTGUI_ERROR_NOT_ON_TURN);
                        }
                    }
                }
            });
            this.JMHint.add(stratItem);
        }
        
        // het hulpmenu
        JMenu help = new JMenu(Message.CLIENTGUI_MENU_HELP);
        item = new JMenuItem(Message.CLIENTGUI_MENU_HELP_EXPLAIN_GAME);
        item.addActionListener(new ActionAdapter()
        {
            public void actionPerformed(ActionEvent e)
            {
                JOptionPane.showMessageDialog(
                        null,
                        Message.CLIENTGUI_MENU_HELP_EXPLANATION,
                        Message.CLIENTGUI_MENU_HELP_EXPLAIN_GAME,
                        JOptionPane.INFORMATION_MESSAGE);
            }
        });
        help.add(item);
        
        // credits
        item = new JMenuItem(Message.CLIENTGUI_MENU_CREDITS);
        item.addActionListener(new ActionAdapter()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                JOptionPane.showMessageDialog(
                        null,
                        Message.CLIENTGUI_MENU_CREDITS_ITSELF,
                        Message.CLIENTGUI_MENU_CREDITS,
                        JOptionPane.INFORMATION_MESSAGE);
            }
        });
        help.add(item);
        
        menuBar.add(help);

        // zet het menu op het frame
        this.setJMenuBar(menuBar);
        
        
        // alle panels toevoegen aan een nieuwe panel met margin van 10 px
        JPanel p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
        p.setBorder(new EmptyBorder(10,10,10,10));
        p.add(topPanel);
        p.add(Box.createVerticalStrut(10));
        p.add(middlePanel);
        
        // panel toevoegen aan contentPane
        Container c = this.getContentPane();
        c.add(p);
    }
    
    /**
     * ActionAdapter voor anonieme klasses binen de GUI
     */
    private abstract class ActionAdapter extends Object implements ActionListener
    {     
       public abstract void actionPerformed(ActionEvent e);
    }
    
    /**
     * Verwijder alle gehighlighte vakken
     */
    private void clearHighlight()
    {
        // alle piece knoppen ontdoen van de eventuele highlighting
        int i = 0;
        while (i < this.pieceButtons.size())
        {
            this.pieceButtons.get(i).setBackground(this.boardPieceColor);
            i++;
        }
        
        // alle vakjes ontdoen van eventuele hghlighting
        i = 0;
        while (i < this.boardButtons.size())
        {
            this.boardButtons.get(i).setBackground(this.boardPieceColor);
            i++;
        }
    }
    
    /**
     * Kleurt het meegegeven stuk op het bord, dit om de hint duidelijk
     * te maken
     * @param piece het te highlighten stuk
     */
    private void setHighlight(Piece piece)
    {
        // verwijder alle voorgaande kleuren
        this.clearHighlight();
        
        // door alle knoppen lussen, kijken of het stuk hetzelfde is
        // als die op het bord, dan een kleur toekennen
        boolean found = false;
        int i = 0;
        while (i < this.pieceButtons.size() && !found)
        {
            String tmpIcon = this.pieceButtons.get(i).getIcon().toString();
            if (tmpIcon.equals(IMAGES_DIR + this.getBinaryNotation(piece.toDecimal()) + ".png"))
            {
                // kleur toekennen
                this.pieceButtons.get(i).setBackground(this.highlightColor);
                found = true;
            }

            i++;
        }
    }
    
    /**
     * Kleurt de meegegeven locatie op het bord, dit voor de
     * hintfunctionaliteit
     * @param location te kleuren locatie
     */
    private void setHighlight(int location)
    {
        this.clearHighlight();
        this.boardButtons.get(location).setBackground(this.highlightColor);
    }
    
    /**
     * Vult de panels voor het board en de pieces met knoppen en icons
     */
    public void fillBoardAndPiecePanel()
    {
        this.boardPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY));
        this.piecePanel.setBorder(BorderFactory.createLineBorder(Color.GRAY));
        
        // arraylists voor de twee keer 16 knoppen en een treeset voor de icons
        this.boardButtons = new ArrayList<JButton>();
        this.pieceButtons = new ArrayList<JButton>();
        this.images       = new TreeMap<String, ImageIcon>();
        
        String    bin;
        JButton   tmpBoardButton;
        JButton   tmpPieceButton;
        ImageIcon tmpImage;
        for (int i = 0; i < NRPIECES; i++)
        {
            bin = getBinaryNotation(i);

            // van het plaatje een icon maken
            tmpImage = new ImageIcon(IMAGES_DIR + bin + ".png");

            // toeveogen aan array
            this.images.put(bin, tmpImage);

            // button aanmaken voor het bord
            tmpBoardButton = new JButton();
            tmpBoardButton.setBorderPainted(false);
            tmpBoardButton.setContentAreaFilled(true);
            tmpBoardButton.setOpaque(true);
            tmpBoardButton.setBackground(this.boardPieceColor);
            this.boardButtons.add(tmpBoardButton);
            this.boardPanel.add(tmpBoardButton);

            // button aanmaken met icon voor de stukken
            tmpPieceButton = new JButton();
            tmpPieceButton.setBorderPainted(false);
            tmpPieceButton.setContentAreaFilled(true);
            tmpPieceButton.setOpaque(true);
            tmpPieceButton.setBackground(this.boardPieceColor);
            this.pieceButtons.add(tmpPieceButton);
            this.piecePanel.add(tmpPieceButton);
        }
        
        // bord resetten met de icons op de juiste plek
        this.resetBoardAndPieces();
    }
    
    /**
     * Reset alle velden in beide panelen voor de stukken
     */
    public void resetBoardAndPieces()
    {
        // initialisatie
        JButton tmpBoardButton;
        JButton tmpPieceButton;
        String bin;
        
        // alle stukken langs
        for (int i = 0; i < NRPIECES; i++)
        {
            tmpBoardButton = this.boardButtons.get(i);
            tmpBoardButton.setIcon(emptyImg);
            
            if (tmpBoardButton.getActionListeners().length == 0)
            {
                tmpBoardButton.addActionListener(this);
            }
            
            bin = getBinaryNotation(i);
            tmpPieceButton = this.pieceButtons.get(i);
            tmpPieceButton.setIcon(images.get(bin));
            
            if (tmpPieceButton.getActionListeners().length == 0)
            {
                tmpPieceButton.addActionListener(this);
            }
            
            tmpPieceButton.setEnabled(true);
        }
        
        // icon labels leeg maken zodra ze gedefinieerd zijn
        if (this.LBOpponentPiece != null && this.LBCurrentPiece != null)
        {
            this.LBOpponentPiece.setIcon(null);
            this.LBCurrentPiece.setIcon(null);
        }
        
        this.clearHighlight();
    }
        
    /**
     * Er heeft een actie plaatgevonden, verwerk deze actie.
     * @param e de actie
     */
    public void actionPerformed (ActionEvent e)
    {
        // de bron van de actie achterhalen
        Object src = e.getSource();
        
        // als de bron het tekstveld is
        if (src == this.TFMessage)
        {
            // als er een bericht is om te sturen
            if (!this.TFMessage.getText().equals(""))
            {
                // de tekst het net opsturen
                this.client.sendMessage(Protocol.MSG + Protocol.DELIM + this.TFMessage.getText());
                this.TFMessage.setText("");
            }
        }
        
        // als we een computerspeler hebben zijn de board en pieces knoppen 
        // niet in gebruik
        else if (this.kindOfPlayer.equals(Client.COMPUTER))
        {
            this.errorMessage(Message.CLIENTGUI_ERROR_COMPUTER_PLAYS);
        }
        
        // als het een knop uit het bord is
        else if (this.boardButtons.contains(src))
        {
            // kijken of je wel aan de beurt bent
            if (this.currentPiece != null)
            {
                this.locationForPiece = this.boardButtons.indexOf((JButton)src);
                ((JButton)src).setIcon(this.LBCurrentPiece.getIcon());
                ((JButton)src).removeActionListener(this);
                try
                {
                    this.notify();
                }
                catch (IllegalMonitorStateException ex) {}
            }
            else
            {
                // foutmelding weergeven
                this.errorMessage(Message.CLIENTGUI_ERROR_WAIT_LOCATION);
            }
        }

        // is er gedrukt op een knop van de beschikbare stukken
        else if (this.pieceButtons.contains(src))
        {
            // ben je wel aan de beurt?
            if (this.selectPiece)
            {
                // zolang je geen geldig stuk hebt gekozen
                while (this.newPiece == null)
                {
                    try
                    {
                        int tmpIndexOf = this.pieceButtons.indexOf(src);
                        this.newPiece = new Piece(tmpIndexOf);
                        ((JButton)src).removeActionListener(this);
                        try
                        {
                            this.notify();
                        }
                        catch (IllegalMonitorStateException ex) {}
                    }
                    catch (InvalidPieceValueException ex)
                    {
                        this.errorMessage(Message.CLIENTGUI_ERROR_INVALID_PIECE);
                        this.newPiece = null;
                    }
                }
            }
            else
            {
                // melding naar de gebruiker
                this.errorMessage(Message.CLIENTGUI_ERROR_WAIT_PIECE);
            }
        }
    }
    
    /**
     * 'Luistert' of de strategie slider verandert, en verandert zo nodig
     * de strategie van de computerspeler
     * @param e
     */
    public void stateChanged(ChangeEvent e)
    {
        // nieuwe strategie toekennen
        this.currentStrategy = ((ComputerPlayer)this.player).getStrategy();
        int index = (int)((JSlider)e.getSource()).getValue() - 1;
        Strategy newStrategy = this.strategies.get(index);
        
        
        // als de huidige en de nieuwe verschillend zijn
        if (!this.currentStrategy.getName().equals(newStrategy.getName()))
        {
            this.currentStrategy = newStrategy;
            this.addMessage(Message.CLIENTGUI_SWITCH_AI + ": " + newStrategy.getName());
            newStrategy.setClient(this.client);
            ((ComputerPlayer)this.player).setStrategy(newStrategy);
        }
    }
    
    /**
     * Laat het bord zien zoals dat nu bekend is bij de client
     */
    public void showBoard()
    {
        Board   board = this.client.getBoard();
        Piece   piece;
        JButton boardBtn;
        JButton pieceBtn;
        String  binary;
        for (int i = 0; i < Board.DIM * Board.DIM; i++)
        {
            piece = board.getPiece(i);
            if (piece != null)
            {
                binary = this.getBinaryNotation(Integer.parseInt(piece.toString()));
                boardBtn = this.boardButtons.get(i);
                boardBtn.setIcon(images.get(binary));
                boardBtn.removeActionListener(this);
                
                // het stuk niet meer beschikbaar maken
                pieceBtn = this.pieceButtons.get(piece.toDecimal());
                pieceBtn.setIcon(this.emptyImg);
                pieceBtn.setEnabled(false);
                pieceBtn.removeActionListener(this);
            }
        }
    }
    
    /**
     * Vragen naar een locatie voor het meegegeven stuk
     * @param piece het stuk
     * @return de locatie
     */
    public int getLocationForPiece(Piece piece)
    {
        this.addMessage(String.format(Message.CLIENTGUI_GET_LOCATION, piece.getDescription()));
        
        this.currentPiece = piece;
        this.locationForPiece = -1;
        
        while (this.locationForPiece == -1)
        {
            // wachten
            try
            {
                this.wait();
            }
            catch (IllegalMonitorStateException ex) {}
            catch (InterruptedException ex) {}
        }
        this.LBCurrentPiece.setIcon(emptyImg);
        this.clearHighlight();
        
        this.currentPiece = null;
        return locationForPiece;
    }

    /**
     * Vragen naar een nieuw stuk voor de tegenstander
     * @return het stuk voor de tegenstander
     */
    public Piece getNewPiece()
    {
        this.addMessage(Message.CLIENTGUI_GET_PIECE);
        
        this.newPiece    = null;
        this.selectPiece = true;
        
        // zolang we geen stuk hebben blijven we loopen
        while (newPiece == null)
        {
            // wachten
            try
            {
                this.wait();
            }
            catch (IllegalMonitorStateException ex) {}
            catch (InterruptedException ex) {}
        }
        
        this.clearHighlight();
        this.selectPiece = false;
        
        return newPiece;
    }
    
    /**
     * Geeft een lijst terug met alle strategieen
     * @return lijst met strategieen
     */
    public ArrayList<Strategy> getStrategies()
    {
        return this.strategies;
    }
    
    /**
     * Geeft een binaire representatie van het meegegeven getal terug.
     * Het karakter "0" wordt voor het resultaat geplakt zodra het binaire 
     * getal niet van zichzelf 4 karakters heeft.
     * @param  i het getal dat omgezet moet worden
     * @return binaire representatie van het getal i (evt aangevuld tot 4 karakters)
     */
    public String getBinaryNotation(int i)
    {
        String res = "";
        try
        {
            // omzetten naar binair
            res = Integer.toString(i, 2);
        }
        catch (NumberFormatException e) {}
        
        // aanvullen met nullen
        while (res.length() < 4)
        {
            res = "0" + res;
        }
        
        return res;
    }
    
    /**
     * Laat het connectie dialoog zien
     */
    public void showConnectDialog()
    {
        // initilidatie
        boolean error;
        ClientConnectionDialog d;
        
        do
        {
            error = false;

            d = new ClientConnectionDialog(this);
            d.setVisible(true);
            
            // soort speler en gebruikersnaam
            this.kindOfPlayer    = d.getPlayerType();
            this.currentStrategy = this.strategies.get(d.getStrategy());
            this.playerName      = d.getUsername();

            // De spelersnaam invullen
            this.LBYourName.setText(this.playerName);
        
            // hostadres
            try 
            { 
                this.hostAddress = InetAddress.getByName(d.getAddress());
            }
            catch (UnknownHostException e)
            {
                error = true;
                this.errorMessage(String.format(Message.ERROR_INVALID_HOST, d.getAddress()));
            }

            // poortnummer
            try
            {
                this.portNumber = Integer.parseInt(d.getPort());
            }
            catch (NumberFormatException e)
            {
                error = true;
                this.errorMessage(String.format(Message.ERROR_INVALID_PORT, d.getPort()));
            }
        }
        // net zolang vragen tot er geen fouten meer zijn
        while (error);
        
        // kijken of er geprobeerd te verbinden
        if (d.tryToConnect())
        {
            // als er een client is gestart, deze sluiten
            if (this.client != null)
            {
                this.client.shutdown();
            }

            this.client = null;
            this.connect();
        }
    }
    
    /**
     * Is de GUI al op het scherm
     * @return gui gestart
     */
    public boolean isGUIStarted()
    {
        return this.connected;
    }
    
    /**
     * Maakt de speler menselijk of een computer
     */
    private void setPlayer()
    {
        // als de gebruiker aangegeven heeft een menselijke speler te willen zijn
        if (this.kindOfPlayer.equals(Client.HUMAN))
        {
            // maak een nieuwe menselijke speler aan
            this.player = new HumanPlayer();
            
            // maak de computer-sterkte-slider onzichtbaar
            this.strategyStrength.setVisible(false);
            
            // maak de hints zichtbaar
            this.JMHint.setVisible(true);
        }
        // computer speler
        else
        {
            // maak een nieuwe computer speler aan
            this.player = new ComputerPlayer();
            
            // geef de 'DumbStrategy' mee zodra er nog geen strategy gekozen was
            if (this.currentStrategy == null)
            {
                this.currentStrategy = new DumbStrategy();
            }
            // strategy toevoegen aan player
            ((ComputerPlayer)this.player).setStrategy(this.currentStrategy);
            
            // maak de hints onzichtbaar
            this.JMHint.setVisible(false);
            
            // strategie updaten in de GUI
            this.JSStrategies.setValue(this.strategies.indexOf(this.currentStrategy) + 1);
        }
        
        if (this.client != null)
        {
            this.player.setClient(this.client);
            this.client.setPlayer(this.player);
        }
    }
    
    /**
     * Probeert een client aan te maken met de gegevens die uit de JDialog
     * zijn gekomen.
     * @param username De gebruikersnaam voor de speler
     * @param address  Het host adres waarop een verbinding gemaakt moet worden
     * @param port     Het poortnummer waarop een verbinding gemaakt moet worden
     */
    public void connect()
    {
        this.setPlayer();
        
        // even laten merken wat we aan het doen zijn
        this.addMessage(Message.CLIENTUI_CONNECTING);

        try
        {
            // we proberen een nieuwe client aan te maken
            client = new Client(this.playerName, this.hostAddress, this.portNumber, this, this.player);

            if (client != null)
            {
                // we zijn geconnect
                this.connected = true;
        
                player.setClient(client);
                client.start();

                // even wat communiceren naar de gebruiker
                this.addMessage(Message.CLIENTUI_CONNECTED);
                
                this.resetBoardAndPieces();
                this.JMIReconnet.setEnabled(false);
            }
        }
        // aanmaken van nieuwe client is mislukt
        catch (IOException e)
        {
            // foutmelding printen
            this.errorMessage("[ERROR] " + e.getMessage());
            
            this.showConnectDialog();
        }
        catch (IllegalArgumentException e)
        {
            // foutmelding printen
            this.errorMessage("[ERROR] " + e.getMessage());
            
            this.showConnectDialog();
        }
    }
    
    /**
     * Vraagt of de gebruiker nog een spelletje wilt spelen. Zo ja, laat 
     * het dialog venster zien waarin gegevens ingevoerd kunnen worden 
     * voor een nieuwe verbinding
     */
    public void askForReconnect()
    {
        // we zijn niet meer verbonden
        this.connected = false;
        
        // haal de winnende vakjes op
        Board board = this.client.getBoard();
        board.hasWinner();
        ArrayList<Integer> fields = board.getWinningFields();
        
        // er heeft iemand gewonnen
        if (fields != null)
        {
            int i = 0;
            while (i < fields.size())
            {
                // kleur stuk voor stuk de locaties
                this.boardButtons.get((int)fields.get(i)).setBackground(this.highlightColor);

                i++;
            }
        }
        
        int j = 0;
        while (j < Board.DIM * Board.DIM)
        {
            this.boardButtons.get(j).removeActionListener(this);
            this.pieceButtons.get(j).removeActionListener(this);
            
            j++;
        }
        
        if (this.shutdown)
        {
            System.exit(0);
        }
        
        this.JMIReconnet.setEnabled(true);
    }
    
    /**
     * Geeft aan onder welke namen het spel gespeeld wordt
     * @param yourName de naam van deze speler
     * @param opponentName de naam van de tegenstander
     */
    public void setPlayerNames(String yourName, String opponentName)
    {
        this.LBYourName.setText(yourName);
        this.LBOpponentName.setText(opponentName);
    }
    
    /**
     * Laat het huidige stuk zien en verwijdert hem uit de lijst van beschikbare stukken
     * @param piece het huidige stuk
     */
    public void showCurrentPiece(Piece piece)
    {
        String binary = this.getBinaryNotation(piece.toDecimal());
        
        // huidige stuk in label laten zien
        this.LBCurrentPiece.setIcon(images.get(binary));
        
        // het huidige stuk niet meer beschikbaar maken
        JButton pieceBtn = this.pieceButtons.get(piece.toDecimal());
        pieceBtn.setIcon(this.emptyImg);
        pieceBtn.removeActionListener(this);
        
        // stuk voor de tegenstander mag weer weg
        this.LBOpponentPiece.setIcon(null);
    }
    
    /**
     * Laat het stuk voor de tegenstander zien in het daarvoor bestemde label
     * @param piece het stuk voor de tegenstander
     */
    public void showOpponentPiece(Piece piece)
    {
        String binary = this.getBinaryNotation(piece.toDecimal());
        
        // Stuk voor tegenstander in label laten zien
        this.LBOpponentPiece.setIcon(images.get(binary));
        
        // het stuk niet meer beschikbaar maken
        JButton pieceBtn = this.pieceButtons.get(piece.toDecimal());
        pieceBtn.setIcon(this.emptyImg);
        
        // huidige stuk mag weg
        this.LBCurrentPiece.setIcon(null);
    }
            
    /**
     * Voegt een bericht toe aan de TextArea met statusberichten
     * @param msg
     */
    public void addMessage(String msg)
    {
        this.TAMessages.append(msg + "\n");
        this.TAMessages.setCaretPosition(this.TAMessages.getText().length());
    }
    
    /**
     * Geeft een melding van een fout in een popup window.
     * @param msg de melding van de fout
     */
    public void errorMessage(String msg)
    {
        JOptionPane.showMessageDialog(
            null,
            msg,
            Message.CLIENTGUI_ERROR_TITLE,
            JOptionPane.ERROR_MESSAGE);
    }

    /**
     * Start een nieuwe ClientGUI op
     */
    public static void main(String[] args)
    {
        new ClientGUI();
    }
}
